/**
 *    Copyright (C) 2008, 2009  Victor Smirnov <aist11@gmail.com>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program; if not, write to the Free Software Foundation, Inc.,
 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *    See LICENSE.exceptions in the root folder of this project for possible
 *    exceptions
 *
 */

#ifndef _CUBEFS_CORE_CONTAINER_MODEL_HPP
#define	_CUBEFS_CORE_CONTAINER_MODEL_HPP

#include <cubefs/api/types.hpp>
#include <cubefs/api/models/logs.hpp>
#include <cubefs/core/tools/types/typelist.hpp>
#include <cubefs/core/tools/reflection.hpp>

#include <cubefs/core/container/names.hpp>
#include <cubefs/core/container/builder.hpp>
#include <cubefs/core/container/iterator.hpp>

#include <string>

#define CUBEFS_SOURCE "cubefs/core/container/model.hpp:" CUBEFS_AT

#define CUBEFS_MODEL_METHOD_IS_NOT_IMPLEMENTED() throw CubefsException(CUBEFS_SOURCE, std::string("Method is not implemented for " + me_.type_name()))

namespace cubefs    {
namespace ctr       {

using cubefs::TypeList;
using namespace cubefs::tools;

template <typename Profile_, typename MyType, typename TypesType, typename ModelTypeName>
class ModelBase: public TypesType::ModelInterface {
public:

	typedef Profile_															Profile;
    typedef ModelTypeName                                                       Name;
    typedef TypesType                                                           Types;

    typedef typename ContainerTypesLocator<Profile>::PageManagerType			PageMgr;
    typedef typename PageMgr::Transaction                                       Txn;
    typedef typename PageMgr::Page                                              Page;
    typedef typename PageMgr::Page::ID                                          PageId;

    typedef typename Types::template IteratorFactory<MyType>::Type     			Iterator;

    template <typename Name>
    struct Models {
        typedef typename Type2TypeMap<
                    Name,
                    typename Types::EmbeddedModelsList
        >::Result                                                               Type;
    };

    static const bool kCompositeModel = !ListSize<typename Types::EmbeddedModelsList>::Value;

private:
    MyType&                     me_;
    PageId                      root_;
    
protected:
    static ModelMetadata*       reflection_;

public:
    ModelBase(MyType &me): me_(me), root_(0) {}

    BigInt IGetRawSize() {
        return -1;
    }

    MyType &me() {
        return me_;
    }

    const MyType &me() const {
        return me_;
    }

    static Int hash() {
        return reflection_->Hash();
    }

    void init_root(const PageId &root) {
        Page* page = me_.mgr().get(root);
        if (page == NULL) {
            throw NullPointerException(CUBEFS_SOURCE, "Requested page is not available");
        }

        if (page->model_hash() != me_.hash()) {
            throw new CubefsException(CUBEFS_SOURCE, "Invalid page type for this model");
        }

        root_ = root;
    }

    void set_root(const PageId &root) {
        me_.mgr().new_root(me_.name(), root);
        root_ = root;
    }

    const PageId &root() const {
        return root_;
    }

    const bool IsComposite() {
        return kCompositeModel;
    }

    static ModelMetadata * reflection() {
        return reflection_;
    }

    virtual TypedIDValue* GetRootID() {
        if (!root_.is_null()) {
        	return new TypedIDValueImpl(&root_);
        }
        else {
            return NULL;
        }
    }
    
    static Model* CreateModel(TypedIDValue* rootID, cubefs::api::Container* container, BigInt name) {

        PageMgr* mgr = static_cast<PageMgr*>(container->internal_page_mgr());

        if (rootID != NULL)
        {
            PageId id(0);
            id.CopyFrom(rootID->GetValue().ptr());

            if (id.is_null()) throw NullPointerException(CUBEFS_SOURCE, "Provided IDValue contains null ID");

            Page* page = mgr->get(id);

            if (page == NULL) throw NullPointerException(CUBEFS_SOURCE, "Unknown root page ID");

            if (name != -1) throw CubefsException(CUBEFS_SOURCE, "-1 name value must be supplied");

            MyType* model = new MyType(*mgr, MyType::GetModelNameFormPage(page));

            model->init_root(id);
            return model;
        }
        else
        {
            if (name == -1) throw CubefsException(CUBEFS_SOURCE, "Valid model name value must be supplied");

            MyType* model = new MyType(*mgr, name);
            model->create_new();
            return model;
        }
    }

    static Int Destroy() {
    	if (reflection_ != NULL) {
    		cout<<"Delete model "<<reflection_<<endl;
    		delete reflection_;
    		reflection_ = NULL;
    	}
    }

    static Int Init() {
        if (reflection_ == NULL) {
            MetadataList list;
            Types::Pages::NodeDispatcher::BuildMetadataList(list);
            PageInitDispatcher<typename Types::DataPageTypesList>::BuildMetadataList(list);

            reflection_ = new ModelMetadataImpl(TypeNameFactory<Name>::name(), list, Name::Code, &CreateModel);
        }
        return reflection_->Hash();
    }
};

template <typename Profile, typename MyType, typename TypesType, typename ModelTypeName>
ModelMetadata* ModelBase<Profile, MyType, TypesType, ModelTypeName>::reflection_ = NULL;


template <
        typename Name,
        typename MyType,
        typename Base
>
class ModelPart;

template<
        typename ModelTypeName,
        typename List,
        typename TypesType
>
struct Model: public TypeAwareBuilder <
        List,
        ModelPart,
        Model<
            ModelTypeName,
            List,
            TypesType
        >,
        typename TypesType::template ModelBaseFactory<
			Model<
				ModelTypeName,
				List,
				TypesType
			>,
			TypesType,
			ModelTypeName
		>::Type
>
{
    typedef Model<ModelTypeName, List, TypesType/*, FactoryType*/>            MyType;
    
    typedef TypeAwareBuilder <
		List,
		ModelPart,
		MyType,
		typename TypesType::template ModelBaseFactory<
			MyType,
			TypesType,
			ModelTypeName
		>::Type
	>																			Base;


    typedef typename TypesType::IteratorPartsList                               IteratorPartsList;
    typedef typename Base::PageMgr                                              PageMgr;

public:

    typedef ModelTypeName                                                       Name;
    typedef typename PageMgr::Transaction                                       Transaction;
    
private:

    MyType& 	me_;

    PageMgr&	mgr_;
    BigInt      name_;
    Int         log_level_;
    String 		model_type_name_;

public:
    typedef TypesType                                                           Types;

    Model(PageMgr &mgr, BigInt name):
        Base(*this),
        me_(*this),
        mgr_(mgr), name_(name), log_level_(-1),
        model_type_name_(TypeNameFactory<ModelTypeName>::name())
    {
    	//CUBEFS_TRACE(me_, "Create Model Object", this);
    }

    virtual ~Model() throw() {
        //CUBEFS_TRACE(me_, "Destroy Model Object", this);
    }

    PageMgr& mgr() {
        return mgr_;
    }

    const std::string& type_name() {
        return model_type_name_;
    }

    bool is_log(Int level)
    {
        if (log_level_ >= 0)
        {
            return level >= log_level_;
        }
        else
        {
            return level >= logger().GetLogLevel();
        }
    }

    cubefs::api::Logger& logger() {
        return mgr_.logger();
    }

    const PageMgr& mgr() const {
        return mgr_;
    }

    Transaction *txn() {
        return NULL;
    }

    BigInt name() const {
        return name_;
    }

    virtual BigInt ModelName() {
        return name();
    }
} ;

}
}

#undef CUBEFS_SOURCE

#endif
